# encoding:utf-8
import re
import time
import copy
import pygame
import serial 
import struct
import threading
from pygame.locals import *
from sys import exit
from prompt_toolkit import prompt
from prompt_toolkit.history import FileHistory
from prompt_toolkit.auto_suggest import AutoSuggestFromHistory
from Misc import BR_Action, BR_completer

# --------------------------- Command List ----------------------------------
# 退出      : exit
# 软件初始化: reset
# 清除错误  : error clear     
# 动作触发  : action trigger act_name pars(此处只会发送前n个参数，n为单片机上该动作初始化设定的参数个数) 

# -----------------------   config   ---------------------------
# serial
com = "COM5"
bps = 115200
tout = 1
SCREEN_SIZE = (540, 400)

# -----------------------   global   ---------------------------
Ser = serial.Serial(com, bps, timeout = tout)
BR_ActionDict = {}
BR_ErrorList = []
_BR_completer = copy.deepcopy(BR_completer)
CPU_Rate = 0

def prvB2i(Byte):
    return struct.unpack('B', Byte)[0]

def prvi2B(Int):
    return struct.pack('B', Int)

def Reset():
    global BR_ActionDict, BR_ErrorList, CPU_Rate, _BR_completer
    BR_ActionDict = {}
    BR_ErrorList = []
    CPU_Rate = 0
    _BR_completer = copy.deepcopy(BR_completer)

def PrintHelp():
    print("Command List:")
    print("退出      : exit")
    print("软件初始化: reset")
    print("清除错误  : error clear")
    print("动作触发  : action trigger act_name pars(此处只会发送前n个参数，n为单片机上该动作初始化设定的参数个数)")

def CmdTask():
    '''
    接收指令并发送任务
    '''
    while True:
        Input = prompt('>>>', history=FileHistory('history.txt'), 
                              auto_suggest=AutoSuggestFromHistory(),
                              completer=_BR_completer)
        Input = re.split(r' ', Input)
        Input = Input[:-1] if Input[-1] == '' else Input  # 防止split结果最后出现空字符
        if len(Input) == 0:
            PrintHelp()
            continue
        if Input[0] == 'exit':  # 退出
            exit()
        elif Input[0]  == 'reset':  # 重设
            Reset()
        elif Input[0] == 'error':
            if Input[1] == 'clear':  # 清空error
                PostMsgByte = b'\x41\x10\x51'
                Ser.write(PostMsgByte)
        elif Input[0] == 'action':
            if Input[1] == 'trigger':
                try:
                    PostMsgByte = b'\x41\x20'
                    Action = BR_ActionDict[Input[2]]
                    PostMsgByte += prvi2B(Action.Index)
                    if len(Input)<(3+Action.ParsNum):
                        print("[Warning] Parameters Number error ! Shoule be {}".format(Action.ParsNum))
                        continue
                    for ParString in Input[3:3+Action.ParsNum]:  # 拿取任务需要参数数目个float
                        ParFloat = float(ParString)
                        PostMsgByte += struct.pack('f', ParFloat)
                    Check = 0
                    for intByte in PostMsgByte:
                        Check += intByte
                    Check = Check%256
                    PostMsgByte += prvi2B(Check)
                    Ser.write(PostMsgByte)
                except KeyError:
                    print("[Warning] Please input correct Action Name !")
        else:  # 不是已知命令，打印帮助
            PrintHelp()

def VisualizeTask():
    '''
    显示任务
    '''
    pygame.init()
    screen = pygame.display.set_mode(SCREEN_SIZE, pygame.NOFRAME, 32)
    font = pygame.font.SysFont("arial", 24)
    fontTitle = pygame.font.SysFont("arial", 40)
    font_height = font.get_linesize()
    fontTitle_height = fontTitle.get_linesize()
    while True:
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                pygame.quit()
            if event.type == 3:  # Keyup
                if event.key == 27:  # Esc
                    exit()
        screen.fill((0, 0, 0))
        y = 5
        screen.blit(fontTitle.render('BR_Console', True, (255,255,255)), (170, y))
        y += 1.1*fontTitle_height
        for Err in BR_ErrorList:
            ErrorText = 'ErrorRaised :'
            ErrorText += ' [ '+Err+' ]'
            screen.blit(font.render(ErrorText, True, (255,255,255)), (80, y))
            y += 1.1*font_height
        for ActName in BR_ActionDict:
            Act = BR_ActionDict[ActName]
            ActionText = 'Action'+str(Act.Index)+' :'
            ActionText += ' [ '+Act.Name+' ]'
            ActionText += ' with '+str(Act.ParsNum)+' Pars'
            screen.blit(font.render(ActionText, True, (255,255,255)), (80, y))
            y += 1.1*font_height
        CPU_RateText = 'CPU Rate : {:.4f}%'.format(CPU_Rate)
        screen.blit(font.render(CPU_RateText, True, (255,255,255)), (80, y))
        pygame.display.update()
        time.sleep(0.5)

def HandlerTask(Ser):
    '''
    处理任务
    '''
    global CPU_Rate
    State = 'initial'
    while True:
        if Ser.in_waiting:          # 若有数据到来
            if State == 'initial':
                if Ser.read() == b'A':
                    State = 'rec_A'
            elif State == 'rec_A' :
                FuncByte = Ser.read()
                if FuncByte == b'\x10': 
                    # ------------------ 已抛出错误帧解析 ------------------------                    
                    CheckSum = prvB2i(b'A')
                    ErrorName = b''
                    CheckSum += prvB2i(b'\x10')
                    tmp = Ser.read()
                    while(tmp != b'\x00'):      # 读取至错误名字符串结束
                        CheckSum += prvB2i(tmp)
                        ErrorName += tmp
                        tmp = Ser.read()
                    CheckRecv = prvB2i(Ser.read())
                    if CheckSum%256 == CheckRecv:   # 校验通过
                        ErrorName = str(ErrorName, 'utf-8')
                        if ErrorName not in BR_ErrorList:
                            BR_ErrorList.append(ErrorName)
                    State = 'initial'
                if FuncByte == b'\x20':
                    # ------------------ 已创建动作帧解析 ------------------------                    
                    CheckSum = prvB2i(b'A')
                    ActionName = b''
                    CheckSum += prvB2i(b'\x20')
                    tmp = Ser.read()
                    while(tmp != b'\x00'):      # 读取至动作名字符串结束
                        CheckSum += prvB2i(tmp)
                        ActionName += tmp
                        tmp = Ser.read()
                    Index = prvB2i(Ser.read())
                    CheckSum += Index
                    ParsNum = prvB2i(Ser.read())
                    CheckSum += ParsNum
                    CheckRecv = prvB2i(Ser.read())
                    if CheckSum%256 == CheckRecv:   # 校验通过
                        ActionName = str(ActionName, encoding='utf-8')
                        if ActionName not in BR_ActionDict:
                            BR_ActionDict[ActionName] = BR_Action(ActionName, Index, ParsNum)
                            _BR_completer.words.append(ActionName)  # 添加补全
                    State = 'initial'
                if FuncByte == b'\x30':  
                    # ------------------ CPU占用率帧解析 ------------------------                    
                    CheckSum = prvB2i(b'A')
                    CheckSum += prvB2i(b'\x30')
                    tmpCPU_RateByte = Ser.read(4)
                    for intByte in tmpCPU_RateByte:
                        CheckSum += intByte
                    CheckRecv = prvB2i(Ser.read())
                    if CheckSum%256 == CheckRecv:   # 校验通过
                        CPU_Rate = struct.unpack('f', tmpCPU_RateByte)[0]
                    State = 'initial'

if __name__ == "__main__":
    T1 = threading.Thread(target=HandlerTask, args=(Ser,))
    T2 = threading.Thread(target=VisualizeTask)
    T1.setDaemon(True)
    T2.setDaemon(True)
    T1.start()
    T2.start()
    CmdTask()
